﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace V32VM
{
    public class Watch
    {
        public delegate void WatchesChangedEvent();
        public static event WatchesChangedEvent OnWatchesChanged;
        public static List<Watch> Watches { get; set; }
        static Watch() { Watches = new List<Watch>(); }
        public static void AddWatch(Watch watch)
        {
            Watches.Add(watch);
            WatchesChanged();
        }
        private static List<Tuple<IMemoryUpdatable, string>> getFormsWithWatch(Watch watch)
        {
            List<Tuple<IMemoryUpdatable, string>> formsWithWatch = new List<Tuple<IMemoryUpdatable, string>>();
            foreach (IMemoryUpdatable updatable in MainForm.ViewForms)
            {
                if (updatable is DisassemblerViewForm)
                {
                    DisassemblerViewForm disassembler = updatable as DisassemblerViewForm;
                    if (disassembler.MemoryStartAddrSource.ContainsWatch(watch))
                        formsWithWatch.Add(new Tuple<IMemoryUpdatable, string>(
                            disassembler,
                            "Disassembly View: "
                                + disassembler.MemoryStartAddrSource.ToString()));
                }
                else if (updatable is MemoryViewForm)
                {
                    MemoryViewForm memoryView = updatable as MemoryViewForm;
                    if (memoryView.MemoryStartAddrSource.ContainsWatch(watch))
                        formsWithWatch.Add(new Tuple<IMemoryUpdatable, string>(
                            memoryView,
                            "Memory View: "
                                + memoryView.MemoryStartAddrSource.ToString()));
                }
            }
            return formsWithWatch;
        }
        private static List<Tuple<Watch, string>> getWatchesWithWatch(Watch watch)
        {
            List<Tuple<Watch, string>> watchesWithWatch = new List<Tuple<Watch, string>>();
            foreach (Watch w in Watch.Watches)
                if (w.Source.ContainsWatch(watch))
                    watchesWithWatch.Add(new Tuple<Watch, string>(w,
                        string.Format("Watch {0}: {1}", w.Name, w.Source.ToString())));
            return watchesWithWatch;
        }
        public static void RemoveWatch(Watch watch)
        {
            List<Tuple<IMemoryUpdatable, string>> formsWithWatch = getFormsWithWatch(watch);
            List<Tuple<Watch, string>> watchesWithWatch = getWatchesWithWatch(watch);

            if (formsWithWatch.Count > 0 || watchesWithWatch.Count > 0)
            {
                int c = formsWithWatch.Count + watchesWithWatch.Count;
                List<string> message = new List<string>();
                message.Add(string.Format("The following {0}object{1} contain{2} watch '{3}' in {4} source{1}:",
                    c > 1 ? c.ToString() + " " : "",
                    c > 1 ? "s" : "",
                    c > 1 ? "" : "s",
                    watch.Name,
                    c > 1 ? "their" : "its"));
                message.Add("");
                message.AddRange(from formWithWatch in formsWithWatch select "    " + formWithWatch.Item2);
                message.AddRange(from watchWithWatch in watchesWithWatch select "    " + watchWithWatch.Item2);
                message.Add("");
                message.Add(string.Format("Removing watch '{0}' will cause the preceding object{1} to be deleted.",
                    watch.Name, c > 1 ? "s" : ""));
                message.Add(string.Format("Are you sure you want to continue and remove watch '{0}'?",
                    watch.Name));

                string question = string.Join(Environment.NewLine, message);
                string header = string.Format("Remove watch '{0}'?", watch.Name);

                if (MessageBox.Show(question, header,
                    MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
                    return;
            }
            foreach (IMemoryUpdatable updatable in (from formWithWatch
                                                    in formsWithWatch
                                                    select formWithWatch.Item1))
                (updatable as Form).Close();
            foreach (Watch w in (from watchWithWatch
                                 in watchesWithWatch
                                 select watchWithWatch.Item1))
                Watches.Remove(w);

            Watches.Remove(watch);
            WatchesChanged();
        }
        public static void WatchesChanged()
        {
            if (OnWatchesChanged != null)
                OnWatchesChanged();
        }


        public string Name { get; private set; }
        public bool IsValueWatch
        {
            get;
            private set;
        }
        public bool IsAddressWatch
        {
            get { return !IsValueWatch; }
            private set { IsValueWatch = !value; }
        }
        public bool IsWord { get; set; }

        public Source Source { get; set; }

        public Watch(string name, Source source)
        {
            Name = name;
            IsValueWatch = true;
            Source = source;
        }
        public Watch(string name, Source source, bool word)
        {
            Name = name;
            IsValueWatch = false;
            Source = source;
            IsWord = word;
        }

        public Tuple<uint, bool> GetHighlightAddress()
        {
            if (IsValueWatch) return null;
            return new Tuple<uint, bool>(Source.GetValue(), IsWord);
        }
        public uint GetValue()
        {
            return Source.GetValue();
        }
        public uint GetAddressedValue()
        {
            if (IsValueWatch) throw new Exception("Call only the GetValue() method if IsValueWatch.");
            if (IsWord)
                return Program.VM.memory.words[Program.VM.regs.ip, Source.GetValue()];
            return Program.VM.memory[Program.VM.regs.ip, Source.GetValue()];
        }

        public override string ToString()
        {
            if (IsValueWatch)
                return Name + ": " + Source.ToString() + ": " + GetValue();
            else
                return Name + ": " + "[" + Source.ToString() + "=" + GetValue() + "]" + (IsWord ? "w" : "b") + ": " + GetAddressedValue();
        }
    }
}
